Repository navigation
Take the black level of RAW images from the optical black - #158
Merged
Merged
Conversation
The black level of the RAW metadata is a nominal value of the camera model. The optical black pixels are masked pixels at the margin of the sensor, read out together with the image itself, so their mean is the black level of this image and follows the drift with the sensor temperature. The two differ by a fraction of a count, and the integration over the search area of an LED turns that difference into a constant offset of its intensity, which stays while the LED dims in smoke and so saturates the extinction coefficients. _black_level resolves this for the only place that subtracts a black level, so it reaches sum_col_val, bgsub_sum_col_val, sidebg_sum_col_val and bgsub2_sum_col_val alike. The metadata value remains the fallback for sensors whose optical black margin is narrower than MIN_OPTICAL_BLACK_WIDTH. JPG and PNG have no black level and are untouched. The values of the four columns above therefore change for RAW images, which the column layout alone does not show, so the step 3 header now records the LEDSA version as the headers of the analysis results already do. The change only reaches existing experiments when step 3 is run again for them. The fake RAW image of the orientation tests gains the margin sizes and so covers the fallback. Unit tests pass, 185, with test_black_level.py for the estimate and the fallback. Acceptance tests not run, and no RAW image is covered by the unit tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
create_binary_data wrote all_parameters.h5 with append=True and nothing removed the file of an earlier run, so every LED ended up in the table once per run and the analysis saw each image as often as the step was repeated. The binary data are derived from the step 3 result files, so they are now rewritten from scratch and the replacement is reported. The column names are taken from the first result file of a channel and used for all of them, so result files of different width cannot be loaded together. That happens when step 3 is run again for part of the images after a LEDSA version that writes further columns was installed. Until now pandas stopped with "Shape of passed values is (n, 12), indices imply (n, 10)", which says nothing about the cause. The width of every file is now compared with the one of the first file and the error names both files, both counts and the re-run of the data extraction. Unit tests pass, 133, with six new ones in test_file_handling.py. They write a coordinate file, so they take the same path through _append_coordinates as a real analysis. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The CI check for text I/O without an encoding is line based, so the encoding of a wrapped call reads as missing to it although Python sees it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Split out of the draft #157, which keeps the parts that are still open. This PR holds only what is finished.
1. The black level of RAW images comes from the optical black
Problem.
sum_col_valcarries an additive offset that does not vanish when the LED dims, so the extinction coefficients saturate in dense smoke. The problem statement of #145 names three sources of it. One is that the black level LEDSA subtracts comes from the RAW metadata, which is a nominal value of the camera model and differs from the actual level of the sensor by a fraction of a count. Integrated over the search area, that fraction becomes a constant offset of the intensity of every LED.Change.
_black_leveltakes the black level from the mean of the optical black pixels of the image itself. These are masked pixels at the margin of the sensor, read out together with the image, so the estimate also follows the drift of the black level with the sensor temperature.raw.black_level_per_channel[channel]stays as the fallback for sensors whose margin is narrower thanMIN_OPTICAL_BLACK_WIDTH(8 px). The package subtracts a black level in exactly one place,_read_img_array_from_raw_file, so the change reachessum_col_val,bgsub_sum_col_val,sidebg_sum_col_valandbgsub2_sum_col_valalike. JPG and PNG have no black level and are untouched.It is not an option, because nobody can reasonably prefer a nominal black level to a measured one, and because an option would turn the background correction into black level handling times spatial estimate, which the layout dispatch by column count cannot carry.
The results do change for RAW images, which breaks the "default results are unchanged" rule that #145 was merged under. That is the purpose of the change. The column layout does not show it, so the step 3 header now records the LEDSA version, as the headers of the analysis results already do.
2. The binary data are rewritten instead of appended
Two pre-existing problems next to #92, found while checking what part 1 breaks:
create_binary_datawroteall_parameters.h5withappend=Trueand nothing removed the file of an earlier run, so every LED ended up in the table once per run and the analysis saw each image as often as the step was repeated. The loop always reads the files 1 to N from the start, so appending could only ever duplicate and was never able to extend the table with images it had not already read. The file is now removed and rewritten, and the replacement is reported.Shape of passed values is (n, 12), indices imply (n, 10), which says nothing about the cause. The error now names both files, both counts and the re-run of the data extraction.Backwards compatibility
config.iniandconfig_analysis.inifiles run unchanged.len(columns) - 3, which holds for every branch of_get_column_names, the two legacy ones included (LEGACY_FIT_COLUMNSat 18 columns and_get_old_columnsat 15 and 4), so the new check rejects no layout that loaded before., LEDSA x.y.zon the first header line is inert:read_tableskips the comment lines and nothing parses the first one.analysis/channel*/all_parameters.h5when re-running the analysis on data extracted with an older version. With part 2 that is no longer silent.Tests
Unit tests pass, 191. New are
test_black_level.pyfor the estimate and the fallback and six tests intest_file_handling.pyfor the two guards. The fake RAW image of the orientation tests gained the margin sizes, so it now covers the fallback as well.Not covered: no RAW image is read by any unit test, so the optical black estimate itself still rests on the comparison against BCL on the Warsaw tests V008 and V009, camera 2. The acceptance tests were not run locally. The unit tests ran under numpy 2.2.6 here, outside the
numpy ~= 1.26.4ofpyproject.toml, so the CI is the first run on the supported version.What is not in here
The other half of the correction stays in the draft #157: dropping the clipping at 0, the
ob_sum_col_valandob_noise_sum_col_valuecolumns, the SNR weighting and the image averaging. Dropping the clipping lets the sums go negative and so changes a contract thatmax_col_value, the fit and the postprocessing rely on, and it needs the nonlinear solver and the stacked analysis to be covered first.🤖 Generated with Claude Code